#pragma once

#include "geobase.h"

namespace visualization
{
	class Quaternion
	{
		// Arithmetic operators
		friend const Quaternion operator + (const Quaternion &q1, const Quaternion &q2);
		friend const Quaternion operator - (const Quaternion &q1, const Quaternion &q2);
		friend const Quaternion operator * (const Quaternion &q1, const Quaternion &q2);
		friend const Quaternion operator * (double s, const Quaternion &q);
		friend const Quaternion operator * (const Quaternion &q, double s);
		friend const Quaternion operator / (const Quaternion &q, double s);

	private:
		Vector v;
		double s;

	public:
		// Constructors
		Quaternion() : v(), s(1)														{ }
		Quaternion(double x, double y, double z, double i_s) : v(x, y, z), s(i_s)		{ }
		Quaternion(const Vector& i_v, double i_s) : v(i_v), s(i_s)						{ }
		Quaternion(const Quaternion& i_q) : v(i_q.Vec()), s(i_q.S())					{ }

		~Quaternion() {}

		// Return pointer to data
		inline const double   S()   const { return s; }
		inline const Vector& Vec() const { return v; }

		// Normalize
		inline double   SqrNorm()   { return s * s + v.Norm(); }
		inline double   Norm()      { return sqrt(SqrNorm()); }
		inline void      Normalize() { (*this) /= Norm();      }
		inline void      Reset()     { v.x=0; v.y=0; v.z=0; s = 1; }

		// Assignment operators
		Quaternion& operator *= (const Quaternion &q);
		inline Quaternion& operator =  (const Quaternion &q)	{ v =  q.Vec(); s =  q.S(); return *this; }
		inline Quaternion& operator += (const Quaternion &q)	{ v += q.Vec(); s += q.S(); return *this; }
		inline Quaternion& operator -= (const Quaternion &q)	{ v -= q.Vec(); s -= q.S(); return *this; }
		inline Quaternion& operator *= (double i_s)			{ v *= i_s; s *= i_s; return *this; }
		inline Quaternion& operator /= (double i_s)			{ v /= i_s;	s /= i_s; return *this; }

		// A useful function, builds a rotation matrix in Matrix based on given quaternion.
		void BuildMatrix(double(&m)[16]);
	};

	const Quaternion operator * (const Quaternion &q1, const Quaternion &q2);

	inline const Quaternion operator - (const Quaternion &q1, const Quaternion &q2)
	{
		return Quaternion(q1.Vec().x-q2.Vec().x, q1.Vec().y-q2.Vec().y, q1.Vec().z-q2.Vec().z, q1.S()-q2.S());
	}

	inline const Quaternion operator + (const Quaternion &q1, const Quaternion &q2)
	{
		return Quaternion(q1.Vec().x+q2.Vec().x, q1.Vec().y+q2.Vec().y, q2.Vec().z+q2.Vec().z, q1.S()+q2.S());
	}

	inline const Quaternion operator * (const Quaternion &q, double s)
	{
		return Quaternion(q.Vec().x * s, q.Vec().y * s, q.Vec().z * s, q.S() * s);
	}

	inline const Quaternion operator * (double s, const Quaternion &q)
	{
		return Quaternion(q.Vec().x * s, q.Vec().y * s, q.Vec().z * s, q.S() * s);
	}

	inline const Quaternion operator / (const Quaternion &q, double s)
	{
		return Quaternion(q.Vec().x / s, q.Vec().y / s, q.Vec().z / s, q.S() / s);
	}
}
